package main

import (
	goredislib "github.com/go-redis/redis/v8"
	"github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v8"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"

	"fmt"
	"log"
	"os"
	"sync"
	"time"
)

//表结构自己生成一下并添加数据
/*
添加数据: Goods:421,Stocks:100
		 Goods:422,Stocks:100
		 Goods:423,Stocks:100
		 Goods:424,Stocks:100
*/
type BaseModel struct {
	ID        int32          `gorm:"primary_key;comment:ID" json:"id"`
	CreatedAt time.Time      `gorm:"column:add_time;comment:创建时间" json:"-"`
	UpdatedAt time.Time      `gorm:"column:update_time;comment:更新时间" json:"-"`
	DeletedAt gorm.DeletedAt `gorm:"comment:删除时间" json:"-"`
	IsDeleted bool           `gorm:"comment:是否删除" json:"-"`
}
type Inventory struct {
	BaseModel
	Goods   int32 `gorm:"type:int;index;comment:商品id"`
	Stocks  int32 `gorm:"type:int;comment:仓库"`
	Version int32 `gorm:"type:int;comment:分布式锁-乐观锁"` //这个没用到  可以不要
}

var DB *gorm.DB
var RedisRs *redsync.Redsync

func InitDB() {
	//自己的mysql   辛苦一下建个数据库：mxshop_inventory_srv2     utf8mb4
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		"root", "123456", "localhost", 3306, "mxshop_inventory_srv2")
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer（日志输出的目标，前缀和日志包含的内容——译者注）
		logger.Config{
			SlowThreshold: time.Second, // 慢 SQL 阈值
			//LogLevel:      logger.Info, // 日志级别
			LogLevel: logger.Silent, // 日志级别
			//IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound（记录未找到）错误
			Colorful: true, // 禁用彩色打印
		},
	)
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
	var err error
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		Logger: newLogger,
	})
	if err != nil {
		panic(err)
	}
}

//初始化redis
func InitRedis() {
	client := goredislib.NewClient(&goredislib.Options{
		//自己的redis
		Addr: fmt.Sprintf("%s:%d", "121.40.213.174", 6301),
	})
	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
	RedisRs = redsync.New(pool)
}

type GoodsInvInfo struct {
	GoodsId int32
	Num     int32
}

//批量扣减库存 - 一个用户开始扣减   要么全成功  要么全失败   不能有一半扣减成功一半扣减失败（事务）
func inventory(goods []GoodsInvInfo) {
	//事务开始
	tx := DB.Begin()
	for _, good := range goods {
		//redis锁初始化
		mutex := RedisRs.NewMutex(fmt.Sprintf("goods_%d", good.GoodsId))
		//获取锁
		if err := mutex.Lock(); err != nil {
			//return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
			panic(err)
		}
		//库存信息
		var inv Inventory
		//查询库存是否存在
		if result := DB.Where(&Inventory{Goods: good.GoodsId}).First(&inv); result.RowsAffected == 0 {
			panic("库存不存在")
		}
		//查询库存是否充足
		if inv.Stocks < good.Num {
			panic("库存不足")
		}
		//扣减库存
		//inv.Stocks -= good.Num
		//tx.Save(&inv)
		if err := tx.Model(&inv).Update("stocks", gorm.Expr("stocks - ?", good.Num)).Error; err != nil {
			tx.Rollback()
			panic(err)
		}
		//释放锁
		if ok, err := mutex.Unlock(); !ok || err != nil {
			tx.Rollback() //回滚之前的操作
			panic(err)
		}

		/*
			这里就会有bug了
			在扣减第一件商品后另一个用户在获取第一件商品的时候数量还是扣减之前的    为什么？？？
			只有第一件会发生扣减少的情况   其他的商品不会
			只有第一件？
		*/
	}
	tx.Commit()
}

func main() {
	//初始化redis 和 mysql
	InitDB()
	InitRedis()
	//设置20个协程来模拟用户
	gNum := 20
	var wg sync.WaitGroup
	wg.Add(gNum)
	for i := 0; i < gNum; i++ {
		go func() {
			defer wg.Done()
			//每个用户批量购买商品2件
			inventory([]GoodsInvInfo{
				{GoodsId: 421, Num: 2},
				{GoodsId: 422, Num: 2},
				{GoodsId: 423, Num: 2},
				{GoodsId: 424, Num: 2},
			})
			fmt.Println("扣减成功")
		}()
	}
	wg.Wait()
}
